#Introduction This analysis was done by Mike Goldweber, Sept 2023. Submitted to DrivenData.org on Oct 6, 2023. This document shows; the step by step process of analyzing the emergency room data provided in the Unsupervised Wisdom contest.

Data Ingestion

The data used in this project is the Primary data set. This contains 115,128 rows of data. The data is pulled into two dataframes. One called df_original, and the other is called df_mapped. The df_mapped set is modified by the example code so that it produces human readable data. the df_original set will be modified by the feature engineer to be usable by the modeling done below.

#You'll want to adjust your file path for your environment
filepath <- "c:\\_working\\Machine-Learning\\UnsupervisedWisdom\\Data\\primary_data.csv"

df_original <- read.csv(filepath)

Setting up the categorical mapping dataframe

The code for the next two blocks was take directly from the Community Code section of the contest website, found at: https://www.drivendata.org/competitions/217/cdc-fall-narratives/community-code/.

library(jsonlite)

mappingfile <- 'c:\\_working\\Machine-Learning\\UnsupervisedWisdom\\Data\\variable_mapping.json'
mapping <- fromJSON(mappingfile)
names(mapping)
 [1] "sex"              "race"             "hispanic"         "alcohol"          "drug"             "body_part"        "body_part_2"     
 [8] "diagnosis"        "diagnosis_2"      "disposition"      "location"         "fire_involvement" "product_1"        "product_2"       
[15] "product_3"       
# Convert to data frames so we can use in joins
mapping_tables <- list()
for (col in names(mapping)) {
    mapping_tables[[col]] <- data.frame(
        ind=as.integer(names(mapping[[col]])),  # change to integer types
        values=unlist(mapping[[col]])
    )
}

Now that the JSON information has been ingested, we’ll map the categories on to the dataframe, which we will call df_mapped.

library(dplyr)

# Load primary data
df_mapped <- df_primary

# Join and replace encoded column
for (col in names(mapping)) {
    df_mapped <- df_mapped %>%
        left_join(mapping_tables[[col]], by=setNames("ind", col)) %>%
        mutate(!!col := values) %>%
        select(-values)
}

Data Clean Up/ Preliminary Feature Engineer

Let’s look at the dataset to get an initial feel for the data quality by running a summary

summary(df_original)
 cpsc_case_number     narrative         treatment_date          age              sex             race         other_race           hispanic       diagnosis    
 Min.   :190103269   Length:115128      Length:115128      Min.   : 65.00   Min.   :1.000   Min.   :0.0000   Length:115128      Min.   :0.000   Min.   :42.00  
 1st Qu.:200255706   Class :character   Class :character   1st Qu.: 72.00   1st Qu.:1.000   1st Qu.:0.0000   Class :character   1st Qu.:0.000   1st Qu.:57.00  
 Median :210527801   Mode  :character   Mode  :character   Median : 79.00   Median :2.000   Median :1.0000   Mode  :character   Median :2.000   Median :57.00  
 Mean   :208188741                                         Mean   : 79.35   Mean   :1.631   Mean   :0.7957                      Mean   :1.259   Mean   :58.69  
 3rd Qu.:220460723                                         3rd Qu.: 86.00   3rd Qu.:2.000   3rd Qu.:1.0000                      3rd Qu.:2.000   3rd Qu.:62.00  
 Max.   :230222638                                         Max.   :112.00   Max.   :2.000   Max.   :6.0000                      Max.   :2.000   Max.   :74.00  
                                                                                                                                                               
 other_diagnosis     diagnosis_2    other_diagnosis_2    body_part      body_part_2     disposition       location     fire_involvement       alcohol       
 Length:115128      Min.   :41.0    Length:115128      Min.   : 0.00   Min.   : 0.00   Min.   :1.000   Min.   :0.000   Min.   :0.0000000   Min.   :0.00000  
 Class :character   1st Qu.:53.0    Class :character   1st Qu.:37.00   1st Qu.:35.00   1st Qu.:1.000   1st Qu.:1.000   1st Qu.:0.0000000   1st Qu.:0.00000  
 Mode  :character   Median :59.0    Mode  :character   Median :75.00   Median :75.00   Median :1.000   Median :1.000   Median :0.0000000   Median :0.00000  
                    Mean   :59.8                       Mean   :65.51   Mean   :63.98   Mean   :2.122   Mean   :1.715   Mean   :0.0005993   Mean   :0.02248  
                    3rd Qu.:63.0                       3rd Qu.:79.00   3rd Qu.:79.00   3rd Qu.:4.000   3rd Qu.:1.000   3rd Qu.:0.0000000   3rd Qu.:0.00000  
                    Max.   :74.0                       Max.   :94.00   Max.   :94.00   Max.   :6.000   Max.   :9.000   Max.   :3.0000000   Max.   :1.00000  
                    NA's   :71983                                      NA's   :71983                                                                        
      drug           product_1      product_2        product_3      
 Min.   :0.00000   Min.   : 110   Min.   :   0.0   Min.   :   0.00  
 1st Qu.:0.00000   1st Qu.:1715   1st Qu.:   0.0   1st Qu.:   0.00  
 Median :0.00000   Median :1807   Median :   0.0   Median :   0.00  
 Mean   :0.04035   Mean   :2168   Mean   : 504.1   Mean   :  56.16  
 3rd Qu.:0.00000   3rd Qu.:3299   3rd Qu.: 474.8   3rd Qu.:   0.00  
 Max.   :1.00000   Max.   :5043   Max.   :5040.0   Max.   :5040.00  
                                                                    

Usually a concern is missing data scattered randomly thoughtout the set. In this case, the summary shows the data is in good shape. That is there isn’t much in the way of missing data. The only NA’s we see are in the diagnosis_2 and body_part_2 columns. This isn’t a suprise, because the ER patients didn’t necessarily suffer secondary injuries. There is a correlation between these two columns, given the identical number of NAs at 71,983. These columns will have to be explored further to determine if it should be used in our modeling.

However, glancing at the data sets directly shows gaps in some of the other columns. For example body_part_2, other_diagnosis and other_diagnosis_2 contain many gaps.

head(df_mapped)

Data Exploration

This is actually the critical portion of the project, and most of the effort was spent on this work in order to understand the scope of the problem. The results of this exploration affected later portions of the exploration. Let’s begin by looking at correlations between the columns

Correlation Plot

library(corrplot)

#we need to use numerical values for the correlation. So, we'll make a subset of the data.
df_numsubset<- df_original[, c(4:6, 8:9, 13, 15:22)]

#visualization matrix of the data, looking for correlations
corrplot(cor(df_numsubset), type= "upper")

We see strong correlations between race and hispanic, product_1 and product_2, as well as product_2 and product_3. Honestly, this doesn’t seem to be very helpful. At least as this stage. Our focus is on the injuries. There are some connections to age and some of the other factors. Including alcohol. So, we’ll have to explore this.

Age

Let’s look at the age category. In particular, let’s see if there is a particular age that is hit harder than another age. The block converts it into a table, and the plot shows the frequency of injuries by age.

library(ggplot2)


#frequency of injuries by age
data <- df_numsubset[, c('age', 'sex')]
df_agefreq <- as.data.frame(table(data$age))
colnames(df_agefreq)[1] <- "age" 
colnames(df_agefreq)[2] <- "frequency" 
#head(df_agefreq)
ggplot(df_agefreq) + 
    geom_count(mapping = aes(x=age, y=frequency)) + 
    labs(title = "Frequency of Injury By Age", x="Age", y="Count")

What is interesting about this plot, it shows that the injury frequency is relatively high (over 3500) until age 88. I am wondering if the fall off is due to some environmental movement, or is the population over 88 shrinking? There is a bit of a plateau for ages 71-80, where each group has over 4000 injuries, except for age 76 with 3993 injuries.

Sex (Gender)

Next, let’s look at the breakdown of sex (gender) in this dataset.

#63% of the injuries are to women

genderlabels <- c("Male", "Female")
gender <- as.vector(df_original[ ,'sex'])
gentable <- as.data.frame(table(gender))

gentable$gender <- mapvalues(gentable$gender, c( "1", "2"), genderlabels)

pie(gentable$Freq, labels = genderlabels, main="Sex Breakdown")


percentoffemales <- (gentable[2, 'Freq']/nrow(df_original)*100)
out <- sprintf("Percentage of females in this dataset: %f)", percentoffemales) #percentage of females in this dataset
out
[1] "Percentage of females in this dataset: 63.115836)"

This plot visually shows the breakdown of sex in this dataset. We see that at 63.1%, females represents the majority of cases in this dataset.

Age and Gender

Next, let’s look at the split by age and gender.

library(ggplot2)
library(sqldf)
library(reshape2)

results <- sqldf('SELECT age, sex, count(sex) AS "frequency"
                        FROM dfmapping
                        GROUP BY age, sex')

# reshape the dataframe into a long format
results <- melt (results, id.vars = c ("sex", "age"))

# plot two lines for different genders
ggplot (results, aes (x = age, y = value, group = sex, color = sex)) +
  geom_line () +
  labs (x = "Age", y = "Injury Frequency", color = "Sex")

This chart shows a significant difference by gender across the ages of the patients. First, this plot confirms the previous pie chart plot by showing the female population suffers greater numbers of injuries across the age spectrum. As we explore further, we’ll have to consider different approaches for helping each gender. Of note, this shows the data set only includes males and females. The other categories are not represented in this dataset.

Data Exploration by Race and Sex

Next, let’s factor in the race of the patients to see if any particular group is affected more than the others. After looking at the upper level data by race, we’ll look at the hispanic breakdown at the population.

::: {column width=50%

Breakdown By Race

We’ll start off with a simple pie chart to see.


if (system.file(package = "waffle") == "") {
  install.packages("waffle")
}

library(ggplot2)
library(waffle)

rcounts <- as.data.frame(table(df_mapped$race))
colnames(rcounts)[1]<-"race"
colnames(rcounts)[2]<-"frequency"

vec <- numeric()
vecS <- character()

for(i in rcounts$frequency)
{
    x <- i/nrow(df_original)
    x <- x*100
    vec <- c(vec, x)
    
    s <- ifelse((x > 7.0), sprintf("%f%%", x), "")#display only meaning values on the pie chart
    vecS <- c(vecS, s)
}

rcounts <- cbind(rcounts, new_col = vec)
colnames(rcounts)[3]<-"percent"

rcounts <- cbind(rcounts, new_col = vecS)
colnames(rcounts)[4]<-"labels"

ggplot(rcounts, aes(x = "", y = percent, fill = race)) +
  geom_bar(stat="identity", width=1) +
  geom_text(aes(label = labels), position = position_stack(vjust = 0.5)) +
  scale_fill_manual(values = c("#E59866", "#FCF3CF", "#AF601A", "#CCCCCC", "#BD0026", "#AAAAAA", "#FAD7AD")) +
  coord_polar("y", start=0)

This chart is problematic, because of the high percentage of N.S. (not stated). So it maybe difficult to accurately identify a racial component to these injuries.

::: {column width=50% ### Hispanic

right ::: ::::

Diagnosis

Next is a look at the diagnosis

Diagnosis Race and Gender

While there is some doubt about

Addition Feature Engineering

library(waffle)

results <- sqldf('SELECT age, sex, count(sex) AS "frequency"
                        FROM dfmapping
                        GROUP BY age, sex')

ggplot(data5) + 
geom_point(mapping = aes(x=diagnosis, y=frequency, color=race, alpha=race, shape=sex)) + 
    labs(title = "Frequency of Injury By Diagnosis, Gender, & race <100", x="Diagnosis", y="Count")

Data Modeling with Hiearchical Model

Using ChatGPT for Analysis of the Narrative Data

LS0tDQp0aXRsZTogIkVtZXJnZW5jeSBSb29tIEZhbGxpbmcgSW5qdXJ5IEFuYWx5c2lzIg0Kb3V0cHV0OiBodG1sX25vdGVib29rDQotLS0NCg0KI0ludHJvZHVjdGlvbg0KVGhpcyBhbmFseXNpcyB3YXMgZG9uZSBieSBNaWtlIEdvbGR3ZWJlciwgU2VwdCAyMDIzLiBTdWJtaXR0ZWQgdG8gRHJpdmVuRGF0YS5vcmcgb24gT2N0IDYsIDIwMjMuIFRoaXMgZG9jdW1lbnQgc2hvd3M7DQp0aGUgc3RlcCBieSBzdGVwIHByb2Nlc3Mgb2YgYW5hbHl6aW5nIHRoZSBlbWVyZ2VuY3kgcm9vbSBkYXRhIHByb3ZpZGVkIGluIHRoZSBVbnN1cGVydmlzZWQgV2lzZG9tIGNvbnRlc3QuDQoNCg0KIyBEYXRhIEluZ2VzdGlvbg0KVGhlIGRhdGEgdXNlZCBpbiB0aGlzIHByb2plY3QgaXMgdGhlIFByaW1hcnkgZGF0YSBzZXQuIFRoaXMgY29udGFpbnMgMTE1LDEyOCByb3dzIG9mIGRhdGEuIFRoZSBkYXRhIGlzIHB1bGxlZCBpbnRvIHR3byBkYXRhZnJhbWVzLiBPbmUgY2FsbGVkIGRmX29yaWdpbmFsLCBhbmQgdGhlIG90aGVyIGlzIGNhbGxlZCBkZl9tYXBwZWQuIFRoZSBkZl9tYXBwZWQgc2V0IGlzIG1vZGlmaWVkIGJ5IHRoZSBleGFtcGxlIGNvZGUgc28gdGhhdCBpdCBwcm9kdWNlcyBodW1hbiByZWFkYWJsZSBkYXRhLiB0aGUgZGZfb3JpZ2luYWwgc2V0IHdpbGwgYmUgbW9kaWZpZWQgYnkgdGhlIGZlYXR1cmUgZW5naW5lZXIgdG8gYmUgdXNhYmxlIGJ5IHRoZSBtb2RlbGluZyBkb25lIGJlbG93Lg0KDQoNCg0KYGBge3J9DQojWW91J2xsIHdhbnQgdG8gYWRqdXN0IHlvdXIgZmlsZSBwYXRoIGZvciB5b3VyIGVudmlyb25tZW50DQpmaWxlcGF0aCA8LSAiYzpcXF93b3JraW5nXFxNYWNoaW5lLUxlYXJuaW5nXFxVbnN1cGVydmlzZWRXaXNkb21cXERhdGFcXHByaW1hcnlfZGF0YS5jc3YiDQoNCmRmX29yaWdpbmFsIDwtIHJlYWQuY3N2KGZpbGVwYXRoKQ0KYGBgDQoNCiMjIFNldHRpbmcgdXAgdGhlIGNhdGVnb3JpY2FsIG1hcHBpbmcgZGF0YWZyYW1lDQpUaGUgY29kZSBmb3IgdGhlIG5leHQgdHdvIGJsb2NrcyB3YXMgdGFrZSBkaXJlY3RseSBmcm9tIHRoZSBDb21tdW5pdHkgQ29kZSBzZWN0aW9uIG9mIHRoZSBjb250ZXN0IHdlYnNpdGUsIGZvdW5kIGF0OiBodHRwczovL3d3dy5kcml2ZW5kYXRhLm9yZy9jb21wZXRpdGlvbnMvMjE3L2NkYy1mYWxsLW5hcnJhdGl2ZXMvY29tbXVuaXR5LWNvZGUvLg0KDQpgYGB7cn0NCmxpYnJhcnkoanNvbmxpdGUpDQoNCm1hcHBpbmdmaWxlIDwtICdjOlxcX3dvcmtpbmdcXE1hY2hpbmUtTGVhcm5pbmdcXFVuc3VwZXJ2aXNlZFdpc2RvbVxcRGF0YVxcdmFyaWFibGVfbWFwcGluZy5qc29uJw0KbWFwcGluZyA8LSBmcm9tSlNPTihtYXBwaW5nZmlsZSkNCm5hbWVzKG1hcHBpbmcpDQoNCiMgQ29udmVydCB0byBkYXRhIGZyYW1lcyBzbyB3ZSBjYW4gdXNlIGluIGpvaW5zDQptYXBwaW5nX3RhYmxlcyA8LSBsaXN0KCkNCmZvciAoY29sIGluIG5hbWVzKG1hcHBpbmcpKSB7DQogICAgbWFwcGluZ190YWJsZXNbW2NvbF1dIDwtIGRhdGEuZnJhbWUoDQogICAgICAgIGluZD1hcy5pbnRlZ2VyKG5hbWVzKG1hcHBpbmdbW2NvbF1dKSksICAjIGNoYW5nZSB0byBpbnRlZ2VyIHR5cGVzDQogICAgICAgIHZhbHVlcz11bmxpc3QobWFwcGluZ1tbY29sXV0pDQogICAgKQ0KfQ0KYGBgDQoNCk5vdyB0aGF0IHRoZSBKU09OIGluZm9ybWF0aW9uIGhhcyBiZWVuIGluZ2VzdGVkLCB3ZSdsbCBtYXAgdGhlIGNhdGVnb3JpZXMgb24gdG8gdGhlIGRhdGFmcmFtZSwgd2hpY2ggd2Ugd2lsbCBjYWxsICoqZGZfbWFwcGVkKiouDQpgYGB7cn0NCmxpYnJhcnkoZHBseXIpDQoNCiMgTG9hZCBwcmltYXJ5IGRhdGENCmRmX21hcHBlZCA8LSBkZl9wcmltYXJ5DQoNCiMgSm9pbiBhbmQgcmVwbGFjZSBlbmNvZGVkIGNvbHVtbg0KZm9yIChjb2wgaW4gbmFtZXMobWFwcGluZykpIHsNCiAgICBkZl9tYXBwZWQgPC0gZGZfbWFwcGVkICU+JQ0KICAgICAgICBsZWZ0X2pvaW4obWFwcGluZ190YWJsZXNbW2NvbF1dLCBieT1zZXROYW1lcygiaW5kIiwgY29sKSkgJT4lDQogICAgICAgIG11dGF0ZSghIWNvbCA6PSB2YWx1ZXMpICU+JQ0KICAgICAgICBzZWxlY3QoLXZhbHVlcykNCn0NCg0KDQpgYGANCg0KDQoNCg0KDQojIERhdGEgQ2xlYW4gVXAvIFByZWxpbWluYXJ5IEZlYXR1cmUgRW5naW5lZXINCkxldCdzIGxvb2sgYXQgdGhlIGRhdGFzZXQgdG8gZ2V0IGFuIGluaXRpYWwgZmVlbCBmb3IgdGhlIGRhdGEgcXVhbGl0eSBieSBydW5uaW5nIGEgc3VtbWFyeQ0KDQpgYGB7cn0NCnN1bW1hcnkoZGZfb3JpZ2luYWwpDQpgYGANClVzdWFsbHkgYSBjb25jZXJuIGlzIG1pc3NpbmcgZGF0YSBzY2F0dGVyZWQgcmFuZG9tbHkgdGhvdWdodG91dCB0aGUgc2V0LiBJbiB0aGlzIGNhc2UsIHRoZSBzdW1tYXJ5IHNob3dzIHRoZSBkYXRhIGlzIGluIGdvb2Qgc2hhcGUuIFRoYXQgaXMgdGhlcmUgaXNuJ3QgbXVjaCBpbiB0aGUgd2F5IG9mIG1pc3NpbmcgZGF0YS4gVGhlIG9ubHkgTkEncyB3ZSBzZWUgYXJlIGluIHRoZSBkaWFnbm9zaXNfMiBhbmQgYm9keV9wYXJ0XzIgY29sdW1ucy4gVGhpcyBpc24ndCBhIHN1cHJpc2UsIGJlY2F1c2UgdGhlIEVSIHBhdGllbnRzIGRpZG4ndCBuZWNlc3NhcmlseSBzdWZmZXIgc2Vjb25kYXJ5IGluanVyaWVzLiBUaGVyZSBpcyBhIGNvcnJlbGF0aW9uIGJldHdlZW4gdGhlc2UgdHdvIGNvbHVtbnMsIGdpdmVuIHRoZSBpZGVudGljYWwgbnVtYmVyIG9mIE5BcyBhdCA3MSw5ODMuIFRoZXNlIGNvbHVtbnMgd2lsbCBoYXZlIHRvIGJlIGV4cGxvcmVkIGZ1cnRoZXIgdG8gZGV0ZXJtaW5lIGlmIGl0IHNob3VsZCBiZSB1c2VkIGluIG91ciBtb2RlbGluZy4NCg0KSG93ZXZlciwgZ2xhbmNpbmcgYXQgdGhlIGRhdGEgc2V0cyBkaXJlY3RseSBzaG93cyBnYXBzIGluIHNvbWUgb2YgdGhlIG90aGVyIGNvbHVtbnMuIEZvciBleGFtcGxlIGJvZHlfcGFydF8yLCBvdGhlcl9kaWFnbm9zaXMgYW5kIG90aGVyX2RpYWdub3Npc18yIGNvbnRhaW4gbWFueSBnYXBzLg0KDQpgYGB7cn0NCiNzY3JvbGwgdG8gdGhlIHJpZ2h0IHRvIHNlZSB0aGUgZW1wdHkgY29sdW1ucyAoZXhhbXBsZSBib2R5X3BhcnRfMiwgb3RoZXJfZGlhZ25vc2lzIGFuZCBvdGhlcl9kaXNhZ25vc2lzXzIpLg0KaGVhZChkZl9tYXBwZWQpDQpgYGANCg0KDQojIERhdGEgRXhwbG9yYXRpb24NClRoaXMgaXMgYWN0dWFsbHkgdGhlIGNyaXRpY2FsIHBvcnRpb24gb2YgdGhlIHByb2plY3QsIGFuZCBtb3N0IG9mIHRoZSBlZmZvcnQgd2FzIHNwZW50IG9uIHRoaXMgd29yayBpbiBvcmRlciB0byB1bmRlcnN0YW5kIHRoZSBzY29wZSBvZiB0aGUgcHJvYmxlbS4gVGhlIHJlc3VsdHMgb2YgdGhpcyBleHBsb3JhdGlvbiBhZmZlY3RlZCBsYXRlciBwb3J0aW9ucyBvZiB0aGUgZXhwbG9yYXRpb24uIExldCdzIGJlZ2luIGJ5IGxvb2tpbmcgYXQgY29ycmVsYXRpb25zIGJldHdlZW4gdGhlIGNvbHVtbnMNCg0KIyMgQ29ycmVsYXRpb24gUGxvdA0KDQpgYGB7cn0NCmxpYnJhcnkoY29ycnBsb3QpDQoNCiN3ZSBuZWVkIHRvIHVzZSBudW1lcmljYWwgdmFsdWVzIGZvciB0aGUgY29ycmVsYXRpb24uIFNvLCB3ZSdsbCBtYWtlIGEgc3Vic2V0IG9mIHRoZSBkYXRhLg0KZGZfbnVtc3Vic2V0PC0gZGZfb3JpZ2luYWxbLCBjKDQ6NiwgODo5LCAxMywgMTU6MjIpXQ0KDQojdmlzdWFsaXphdGlvbiBtYXRyaXggb2YgdGhlIGRhdGEsIGxvb2tpbmcgZm9yIGNvcnJlbGF0aW9ucw0KY29ycnBsb3QoY29yKGRmX251bXN1YnNldCksIHR5cGU9ICJ1cHBlciIpDQpgYGANCldlIHNlZSBzdHJvbmcgY29ycmVsYXRpb25zIGJldHdlZW4gcmFjZSBhbmQgaGlzcGFuaWMsIHByb2R1Y3RfMSBhbmQgcHJvZHVjdF8yLCBhcyB3ZWxsIGFzIHByb2R1Y3RfMiBhbmQgcHJvZHVjdF8zLiBIb25lc3RseSwgdGhpcyBkb2Vzbid0IHNlZW0gdG8gYmUgdmVyeSBoZWxwZnVsLiBBdCBsZWFzdCBhcyB0aGlzIHN0YWdlLiBPdXIgZm9jdXMgaXMgb24gdGhlIGluanVyaWVzLiBUaGVyZSBhcmUgc29tZSBjb25uZWN0aW9ucyB0byBhZ2UgYW5kIHNvbWUgb2YgdGhlIG90aGVyIGZhY3RvcnMuIEluY2x1ZGluZyBhbGNvaG9sLiBTbywgd2UnbGwgaGF2ZSB0byBleHBsb3JlIHRoaXMuDQoNCiMjIEFnZQ0KTGV0J3MgbG9vayBhdCB0aGUgYWdlIGNhdGVnb3J5LiBJbiBwYXJ0aWN1bGFyLCBsZXQncyBzZWUgaWYgdGhlcmUgaXMgYSBwYXJ0aWN1bGFyIGFnZSB0aGF0IGlzIGhpdCBoYXJkZXIgdGhhbiBhbm90aGVyIGFnZS4gVGhlIGJsb2NrIGNvbnZlcnRzIGl0IGludG8gYSB0YWJsZSwgYW5kIHRoZSBwbG90IHNob3dzIHRoZSBmcmVxdWVuY3kgb2YgaW5qdXJpZXMgYnkgYWdlLg0KYGBge3J9DQpsaWJyYXJ5KGdncGxvdDIpDQoNCg0KI2ZyZXF1ZW5jeSBvZiBpbmp1cmllcyBieSBhZ2UNCmRhdGEgPC0gZGZfbnVtc3Vic2V0WywgYygnYWdlJywgJ3NleCcpXQ0KZGZfYWdlZnJlcSA8LSBhcy5kYXRhLmZyYW1lKHRhYmxlKGRhdGEkYWdlKSkNCmNvbG5hbWVzKGRmX2FnZWZyZXEpWzFdIDwtICJhZ2UiIA0KY29sbmFtZXMoZGZfYWdlZnJlcSlbMl0gPC0gImZyZXF1ZW5jeSIgDQojaGVhZChkZl9hZ2VmcmVxKQ0KZ2dwbG90KGRmX2FnZWZyZXEpICsgDQogICAgZ2VvbV9jb3VudChtYXBwaW5nID0gYWVzKHg9YWdlLCB5PWZyZXF1ZW5jeSkpICsgDQogICAgbGFicyh0aXRsZSA9ICJGcmVxdWVuY3kgb2YgSW5qdXJ5IEJ5IEFnZSIsIHg9IkFnZSIsIHk9IkNvdW50IikNCmBgYA0KV2hhdCBpcyBpbnRlcmVzdGluZyBhYm91dCB0aGlzIHBsb3QsIGl0IHNob3dzIHRoYXQgdGhlIGluanVyeSBmcmVxdWVuY3kgaXMgcmVsYXRpdmVseSBoaWdoIChvdmVyIDM1MDApIHVudGlsIGFnZSA4OC4gSSBhbSB3b25kZXJpbmcgaWYgdGhlIGZhbGwgb2ZmIGlzIGR1ZSB0byBzb21lIGVudmlyb25tZW50YWwgbW92ZW1lbnQsIG9yIGlzIHRoZSBwb3B1bGF0aW9uIG92ZXIgODggc2hyaW5raW5nPyBUaGVyZSBpcyBhIGJpdCBvZiBhIHBsYXRlYXUgZm9yIGFnZXMgNzEtODAsIHdoZXJlIGVhY2ggZ3JvdXAgaGFzIG92ZXIgNDAwMCBpbmp1cmllcywgZXhjZXB0IGZvciBhZ2UgNzYgd2l0aCAzOTkzIGluanVyaWVzLiANCg0KIyMgU2V4IChHZW5kZXIpDQpOZXh0LCBsZXQncyBsb29rIGF0IHRoZSBicmVha2Rvd24gb2Ygc2V4IChnZW5kZXIpIGluIHRoaXMgZGF0YXNldC4NCg0KYGBge3J9DQojNjMlIG9mIHRoZSBpbmp1cmllcyBhcmUgdG8gd29tZW4NCg0KZ2VuZGVybGFiZWxzIDwtIGMoIk1hbGUiLCAiRmVtYWxlIikNCmdlbmRlciA8LSBhcy52ZWN0b3IoZGZfb3JpZ2luYWxbICwnc2V4J10pDQpnZW50YWJsZSA8LSBhcy5kYXRhLmZyYW1lKHRhYmxlKGdlbmRlcikpDQoNCmdlbnRhYmxlJGdlbmRlciA8LSBtYXB2YWx1ZXMoZ2VudGFibGUkZ2VuZGVyLCBjKCAiMSIsICIyIiksIGdlbmRlcmxhYmVscykNCg0KcGllKGdlbnRhYmxlJEZyZXEsIGxhYmVscyA9IGdlbmRlcmxhYmVscywgbWFpbj0iU2V4IEJyZWFrZG93biIpDQoNCnBlcmNlbnRvZmZlbWFsZXMgPC0gKGdlbnRhYmxlWzIsICdGcmVxJ10vbnJvdyhkZl9vcmlnaW5hbCkqMTAwKQ0Kb3V0IDwtIHNwcmludGYoIlBlcmNlbnRhZ2Ugb2YgZmVtYWxlcyBpbiB0aGlzIGRhdGFzZXQ6ICVmKSIsIHBlcmNlbnRvZmZlbWFsZXMpICNwZXJjZW50YWdlIG9mIGZlbWFsZXMgaW4gdGhpcyBkYXRhc2V0DQpvdXQNCg0KYGBgDQoNCmBgYHtyLCBpbmNsdWRlPUZBTFNFfQ0KI2NsZWFudXANCnJtKGdlbmRlciwgZ2VudGFibGUsIGdlbmRlcmxhYmxlcywgb3V0KQ0KYGBgDQoNCg0KVGhpcyBwbG90IHZpc3VhbGx5IHNob3dzIHRoZSBicmVha2Rvd24gb2Ygc2V4IGluIHRoaXMgZGF0YXNldC4gV2Ugc2VlIHRoYXQgYXQgKio2My4xJSoqLCBmZW1hbGVzIHJlcHJlc2VudHMgdGhlIG1ham9yaXR5IG9mIGNhc2VzIGluIHRoaXMgZGF0YXNldC4NCg0KIyMjIEFnZSBhbmQgR2VuZGVyDQpOZXh0LCBsZXQncyBsb29rIGF0IHRoZSBzcGxpdCBieSBhZ2UgYW5kIGdlbmRlci4NCg0KYGBge3J9DQpsaWJyYXJ5KGdncGxvdDIpDQpsaWJyYXJ5KHNxbGRmKQ0KbGlicmFyeShyZXNoYXBlMikNCg0KcmVzdWx0cyA8LSBzcWxkZignU0VMRUNUIGFnZSwgc2V4LCBjb3VudChzZXgpIEFTICJmcmVxdWVuY3kiDQogICAgICAgICAgICAgICAgICAgICAgICBGUk9NIGRmbWFwcGluZw0KICAgICAgICAgICAgICAgICAgICAgICAgR1JPVVAgQlkgYWdlLCBzZXgnKQ0KDQojIHJlc2hhcGUgdGhlIGRhdGFmcmFtZSBpbnRvIGEgbG9uZyBmb3JtYXQNCnJlc3VsdHMgPC0gbWVsdCAocmVzdWx0cywgaWQudmFycyA9IGMgKCJzZXgiLCAiYWdlIikpDQoNCiMgcGxvdCB0d28gbGluZXMgZm9yIGRpZmZlcmVudCBnZW5kZXJzDQpnZ3Bsb3QgKHJlc3VsdHMsIGFlcyAoeCA9IGFnZSwgeSA9IHZhbHVlLCBncm91cCA9IHNleCwgY29sb3IgPSBzZXgpKSArDQogIGdlb21fbGluZSAoKSArDQogIGxhYnMgKHggPSAiQWdlIiwgeSA9ICJJbmp1cnkgRnJlcXVlbmN5IiwgY29sb3IgPSAiU2V4IikNCmBgYA0KDQpgYGB7ciwgaW5jbHVkZT1GQUxTRX0gIA0Kcm0ocmVzdWx0cykNCmBgYA0KDQoNClRoaXMgY2hhcnQgc2hvd3MgYSBzaWduaWZpY2FudCBkaWZmZXJlbmNlIGJ5IGdlbmRlciBhY3Jvc3MgdGhlIGFnZXMgb2YgdGhlIHBhdGllbnRzLiBGaXJzdCwgdGhpcyBwbG90IGNvbmZpcm1zIHRoZSBwcmV2aW91cyBwaWUgY2hhcnQgcGxvdCBieSBzaG93aW5nIHRoZSBmZW1hbGUgcG9wdWxhdGlvbiBzdWZmZXJzIGdyZWF0ZXIgbnVtYmVycyBvZiBpbmp1cmllcyAqYWNyb3NzKiB0aGUgYWdlIHNwZWN0cnVtLiBBcyB3ZSBleHBsb3JlIGZ1cnRoZXIsIHdlJ2xsIGhhdmUgdG8gY29uc2lkZXIgZGlmZmVyZW50IGFwcHJvYWNoZXMgZm9yIGhlbHBpbmcgZWFjaCBnZW5kZXIuIE9mIG5vdGUsIHRoaXMgc2hvd3MgdGhlIGRhdGEgc2V0IG9ubHkgaW5jbHVkZXMgbWFsZXMgYW5kIGZlbWFsZXMuIFRoZSBvdGhlciBjYXRlZ29yaWVzIGFyZSBub3QgcmVwcmVzZW50ZWQgaW4gdGhpcyBkYXRhc2V0Lg0KDQoNCg0KIyMgRGF0YSBFeHBsb3JhdGlvbiBieSBSYWNlIGFuZCBTZXgNCk5leHQsIGxldCdzIGZhY3RvciBpbiB0aGUgcmFjZSBvZiB0aGUgcGF0aWVudHMgdG8gc2VlIGlmIGFueSBwYXJ0aWN1bGFyIGdyb3VwIGlzIGFmZmVjdGVkIG1vcmUgdGhhbiB0aGUgb3RoZXJzLiBBZnRlciBsb29raW5nIGF0IHRoZSB1cHBlciBsZXZlbCBkYXRhIGJ5IHJhY2UsIHdlJ2xsIGxvb2sgYXQgdGhlIGhpc3BhbmljIGJyZWFrZG93biBhdCB0aGUgcG9wdWxhdGlvbi4NCg0KOjo6OiB7LmNvbHVtbnN9DQo6Ojoge2NvbHVtbiB3aWR0aD01MCUNCg0KDQojIyMgQnJlYWtkb3duIEJ5IFJhY2UNCldlJ2xsIHN0YXJ0IG9mZiB3aXRoIGEgc2ltcGxlIHBpZSBjaGFydCB0byBzZWUuDQoNCg0KYGBge3J9DQoNCmlmIChzeXN0ZW0uZmlsZShwYWNrYWdlID0gIndhZmZsZSIpID09ICIiKSB7DQogIGluc3RhbGwucGFja2FnZXMoIndhZmZsZSIpDQp9DQoNCmxpYnJhcnkoZ2dwbG90MikNCmxpYnJhcnkod2FmZmxlKQ0KDQpyY291bnRzIDwtIGFzLmRhdGEuZnJhbWUodGFibGUoZGZfbWFwcGVkJHJhY2UpKQ0KY29sbmFtZXMocmNvdW50cylbMV08LSJyYWNlIg0KY29sbmFtZXMocmNvdW50cylbMl08LSJmcmVxdWVuY3kiDQoNCnZlYyA8LSBudW1lcmljKCkNCnZlY1MgPC0gY2hhcmFjdGVyKCkNCg0KZm9yKGkgaW4gcmNvdW50cyRmcmVxdWVuY3kpDQp7DQogICAgeCA8LSBpL25yb3coZGZfb3JpZ2luYWwpDQogICAgeCA8LSB4KjEwMA0KICAgIHZlYyA8LSBjKHZlYywgeCkNCiAgICANCiAgICBzIDwtIGlmZWxzZSgoeCA+IDcuMCksIHNwcmludGYoIiVmJSUiLCB4KSwgIiIpI2Rpc3BsYXkgb25seSBtZWFuaW5nIHZhbHVlcyBvbiB0aGUgcGllIGNoYXJ0DQogICAgdmVjUyA8LSBjKHZlY1MsIHMpDQp9DQoNCnJjb3VudHMgPC0gY2JpbmQocmNvdW50cywgbmV3X2NvbCA9IHZlYykNCmNvbG5hbWVzKHJjb3VudHMpWzNdPC0icGVyY2VudCINCg0KcmNvdW50cyA8LSBjYmluZChyY291bnRzLCBuZXdfY29sID0gdmVjUykNCmNvbG5hbWVzKHJjb3VudHMpWzRdPC0ibGFiZWxzIg0KDQpnZ3Bsb3QocmNvdW50cywgYWVzKHggPSAiIiwgeSA9IHBlcmNlbnQsIGZpbGwgPSByYWNlKSkgKw0KICBnZW9tX2JhcihzdGF0PSJpZGVudGl0eSIsIHdpZHRoPTEpICsNCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbCA9IGxhYmVscyksIHBvc2l0aW9uID0gcG9zaXRpb25fc3RhY2sodmp1c3QgPSAwLjUpKSArDQogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGMoIiNFNTk4NjYiLCAiI0ZDRjNDRiIsICIjQUY2MDFBIiwgIiNDQ0NDQ0MiLCAiI0JEMDAyNiIsICIjQUFBQUFBIiwgIiNGQUQ3QUQiKSkgKw0KICBjb29yZF9wb2xhcigieSIsIHN0YXJ0PTApDQpgYGAgICAgDQoNCmBgYHtyLCBpbmNsdWRlPUZBTFNFfSAgDQpybSh2ZWMsIHZlY1MsIHgsIHMpDQpgYGANClRoaXMgY2hhcnQgaXMgcHJvYmxlbWF0aWMsIGJlY2F1c2Ugb2YgdGhlIGhpZ2ggcGVyY2VudGFnZSBvZiBOLlMuIChub3Qgc3RhdGVkKS4gU28gaXQgbWF5YmUgZGlmZmljdWx0IHRvIGFjY3VyYXRlbHkgaWRlbnRpZnkgYSByYWNpYWwgY29tcG9uZW50IHRvIHRoZXNlIGluanVyaWVzLiANCjo6Og0KOjo6IHtjb2x1bW4gd2lkdGg9NTAlDQojIyMgSGlzcGFuaWMNCg0KDQoNCnJpZ2h0DQo6OjoNCjo6OjoNCg0KDQojIyBEaWFnbm9zaXMNCk5leHQgaXMgYSBsb29rIGF0IHRoZSBkaWFnbm9zaXMNCg0KIyMgRGlhZ25vc2lzIFJhY2UgYW5kIEdlbmRlcg0KV2hpbGUgdGhlcmUgaXMgc29tZSBkb3VidCBhYm91dA0KDQoNCiMgQWRkaXRpb24gRmVhdHVyZSBFbmdpbmVlcmluZw0KDQpgYGB7cn0NCmxpYnJhcnkod2FmZmxlKQ0KDQpyZXN1bHRzIDwtIHNxbGRmKCdTRUxFQ1QgYWdlLCBzZXgsIGNvdW50KHNleCkgQVMgImZyZXF1ZW5jeSINCiAgICAgICAgICAgICAgICAgICAgICAgIEZST00gZGZtYXBwaW5nDQogICAgICAgICAgICAgICAgICAgICAgICBHUk9VUCBCWSBhZ2UsIHNleCcpDQoNCmdncGxvdChkYXRhNSkgKyANCmdlb21fcG9pbnQobWFwcGluZyA9IGFlcyh4PWRpYWdub3NpcywgeT1mcmVxdWVuY3ksIGNvbG9yPXJhY2UsIGFscGhhPXJhY2UsIHNoYXBlPXNleCkpICsgDQogICAgbGFicyh0aXRsZSA9ICJGcmVxdWVuY3kgb2YgSW5qdXJ5IEJ5IERpYWdub3NpcywgR2VuZGVyLCAmIHJhY2UgPDEwMCIsIHg9IkRpYWdub3NpcyIsIHk9IkNvdW50IikNCmBgYA0KDQojIERhdGEgTW9kZWxpbmcgd2l0aCBIaWVhcmNoaWNhbCBNb2RlbA0KDQpgYGB7cn0NCg0KYGBgDQoNCiMgVXNpbmcgQ2hhdEdQVCBmb3IgQW5hbHlzaXMgb2YgdGhlIE5hcnJhdGl2ZSBEYXRhDQoNCmBgYHtyfQ0KDQpgYGANCg0K